package org.geektimes.context;

import org.geektimes.function.ThrowableAction;
import org.geektimes.function.ThrowableFunction;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.naming.*;
import javax.servlet.ServletContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 组件上下文（Web 应用全局使用）
 */
public class ClassicComponentContext implements ComponentContext {

    public static final String CONTEXT_NAME = ClassicComponentContext.class.getName();
    private static final Logger logger = Logger.getLogger(CONTEXT_NAME);
    private static ServletContext servletContext;
    private ClassLoader classLoader;
    private Map<String, Object> componentsCache = new LinkedHashMap<>();
    /**
     * @PreDestroy 方法缓存，Key 为标注方法，Value 为方法所属对象
     */
    private Map<Method, Object> preDestroyMethodCache = new LinkedHashMap<>();

    /**
     * 获取 ComponentContext
     *
     * @return
     */
    public static ClassicComponentContext getInstance() {
        return (ClassicComponentContext) servletContext.getAttribute(CONTEXT_NAME);
    }

    private Context envContext;

    public void init(ServletContext servletContext) throws RuntimeException {
        servletContext.setAttribute(CONTEXT_NAME, this);
        ClassicComponentContext.servletContext = servletContext;
        this.init();
    }

    @Override
    public void init() {
        this.initClassLoader();
        // 初始化JNDI上下文
        this.initEnvContext();
        // 实例化组件
        this.instantiateComponents();
        // 初始化组件
        this.initializeComponents();
        //
        this.registerShutdownHook();
    }

    private void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            processPreDestroy();

        }));
    }

    private void initClassLoader() {
        this.classLoader = servletContext.getClassLoader();
    }


    private void initEnvContext() {
        try {
            this.envContext = (Context) new InitialContext().lookup("java:comp/env");
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 初始化组件（支持 Java 标准 Commons Annotation 生命周期）
     * <ol>
     *  <li>注入阶段 - {@link Resource}</li>
     *  <li>初始阶段 - {@link PostConstruct}</li>
     *  <li>销毁阶段 - {@link PreDestroy}</li>
     * </ol>
     */
    protected void initializeComponents() {
        componentsCache.values().forEach(this::initializeComponent);
    }

    private void initializeComponent(Object component) {
        Class<?> componentClass = component.getClass();
        // 注入阶段 - {@link Resource}
        injectComponents(component, componentClass);
        // 查询候选方法
        List<Method> candidateMethods = findCandidateMethods(componentClass);
        // 初始阶段 - {@link PostConstruct}
        processPostConstruct(component, candidateMethods);
        // 本阶段处理 {@link PreDestroy} 方法元数据
        processPreDestroyMetadata(component, candidateMethods);
    }

    private void processPreDestroyMetadata(Object component, List<Method> candidateMethods) {
        candidateMethods.stream()
                .filter(method -> method.isAnnotationPresent(PreDestroy.class))
                .forEach(method -> preDestroyMethodCache.put(method, component));
    }

    private List<Method> findCandidateMethods(Class<?> componentClass) {
        return Stream.of(componentClass.getMethods())
                .filter(method -> {
                    boolean isStatic = Modifier.isStatic(method.getModifiers());
                    boolean isNoParam = method.getParameterCount() == 0;
                    return !isStatic && isNoParam;
                }).collect(Collectors.toList());
    }

    public void injectComponent(Object component) {
        this.injectComponents(component, component.getClass());
    }

    protected void injectComponents(Object component, Class<?> componentClass) {
        Stream.of(componentClass.getDeclaredFields()).filter(field -> {
            int modifiers = field.getModifiers();
            return field.isAnnotationPresent(Resource.class) && !Modifier.isStatic(modifiers);
        }).forEach(field -> {
            Resource resource = field.getAnnotation(Resource.class);
            String name = resource.name();
            Object injectObject = getComponent(name);
            field.setAccessible(true);
            try {
                field.set(component, injectObject);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });

    }

    private void processPostConstruct(Object component, List<Method> candidateMethods) {
        candidateMethods.stream()
                .filter(method -> method.isAnnotationPresent(PostConstruct.class))
                .forEach(method -> {
                    ThrowableAction.execute(() -> method.invoke(component));
                });
    }

    private void processPreDestroy() {
        for (Method method : preDestroyMethodCache.keySet()) {
            // 移除集合中的对象，防止重复执行 @PreDestroy 方法
            Object component = preDestroyMethodCache.remove(method);
            // 执行销毁方法
            ThrowableAction.execute(() -> method.invoke(component));
        }
    }

    private void instantiateComponents() {
        List<String> listAllComponentNames = this.listAllComponentNames();
        listAllComponentNames.forEach(name -> componentsCache.put(name, lookupComponent(name)));
    }

    public <C> C lookupComponent(String name) {
        return excuteInContext(context -> (C) context.lookup(name));
    }

    private List<String> listAllComponentNames() {
        return listComponentNames("/");
    }

    private List<String> listComponentNames(String name) {
        ThrowableFunction<Context, List<String>> function = context -> {
            NamingEnumeration<NameClassPair> namingEnumeration = excuteInContext(ctx -> ctx.list(name), false);
            if (null == namingEnumeration) {
                return Collections.<String>emptyList();
            }

            List<String> fullNames = new LinkedList();
            while (namingEnumeration.hasMoreElements()) {
                NameClassPair nameClassPair = namingEnumeration.nextElement();
                String className = nameClassPair.getClassName();
                Class<?> loadClass = classLoader.loadClass(className);
                String pairName = nameClassPair.getName();
                if (Context.class.isAssignableFrom(loadClass)) {
                    fullNames.addAll(listComponentNames(pairName));
                } else {
                    String fullName = name.startsWith("/") ? name : name + "/" + pairName;
                    fullNames.add(fullName);
                }
            }
            return fullNames;
        };
        return excuteInContext(function);
    }

    /**
     * @param function
     * @param <R>
     * @return
     */
    private <R> R excuteInContext(ThrowableFunction<Context, R> function) {
        return excuteInContext(function, false);
    }

    /**
     * @param function
     * @param ignoreExcpetion
     * @param <R>
     * @return
     */
    private <R> R excuteInContext(ThrowableFunction<Context, R> function, boolean ignoreExcpetion) {
        return excuteInContext(this.envContext, function, ignoreExcpetion);
    }

    /**
     * @param envContext
     * @param function
     * @param ignoreExcpetion
     * @param <R>
     * @return
     */
    private <R> R excuteInContext(Context envContext, ThrowableFunction<Context, R> function, boolean ignoreExcpetion) {
        R result = null;
        try {
            result = function.execute(envContext);
        } catch (Throwable e) {
            if (ignoreExcpetion) {
                logger.warning(e.getMessage());
            } else {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    /**
     * 通过名称进行依赖查找
     *
     * @param name
     * @param <C>
     * @return
     */
    @Override
    public <C> C getComponent(String name) throws RuntimeException {
        C component = (C) componentsCache.get(name);
        if (null != component) {
            return component;
        }
        try {
            component = (C) envContext.lookup(name);
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
        return component;
    }

    @Override
    public Set<String> getComponentNames() {
        return componentsCache.keySet();
    }

    @Override
    public void destroy() throws RuntimeException {
        this.processPreDestroy();
        this.clearCache();
        this.closeEnvContext();
    }

    private void closeEnvContext() {
        this.close(envContext);
    }

    private void close(Context envContext) {
        if (envContext != null) {
            ThrowableAction.execute(envContext::close);
        }
    }

    private void clearCache() {
        componentsCache.clear();
        preDestroyMethodCache.clear();
    }
}
